Popular Searches
Popular Course Categories
Popular Courses

Building Login, Registration, and Contact Forms

Building Login, Registration, and Contact Forms

Forms, Validation, and User Input Handling

Building Login, Registration, and Contact Forms in React Native

Course: React Native Training

Topic: Building Login, Registration, and Contact Forms

Course Link: React Native Training Course at JustAcademy

Demo Link: Register for a Free Course Demo

JustAcademy Website: Visit JustAcademy


1. Introduction to Forms in React Native

Forms are essential components of mobile applications because they allow users to enter information, create accounts, log in, contact businesses, submit feedback, and complete other tasks. In React Native, forms are built using components such as TextInput, Text, Pressable, TouchableOpacity, View, and ScrollView.

A well-designed form should be easy to understand, responsive to different screen sizes, accessible, and capable of validating user input. It should also provide clear feedback when users enter incorrect information or submit a request.

Login, registration, and contact forms share several building blocks, but their purposes and validation requirements are different.

Form Type Purpose Common Fields
Login Form Authenticates an existing user. Email or username, password.
Registration Form Creates a new user account. Full name, email, password, confirm password.
Contact Form Collects inquiries or messages. Name, email, subject, message.

2. Learning Objectives

After completing this topic, you should be able to:

  • Understand how forms work in React Native applications.
  • Build login, registration, and contact form interfaces.
  • Manage input fields using React state and event handlers.
  • Choose suitable keyboard types for different inputs.
  • Validate required fields and common input formats.
  • Display field-level errors and submission feedback.
  • Handle loading states and prevent repeated submissions.
  • Understand how forms communicate with backend APIs.
  • Apply basic accessibility, security, and usability practices.
  • Test forms on different devices and screen sizes.

3. Core React Native Components Used in Forms

3.1 View

View is a container used to group and arrange form elements. It can hold labels, input fields, error messages, buttons, and other components.

import { View, Text } from 'react-native';

function FormHeader() {
  return (
    <View>
      <Text>Welcome Back</Text>
      <Text>Please enter your details.</Text>
    </View>
  );
}

3.2 Text

The Text component displays form titles, field labels, helper text, validation errors, and submission messages.

<Text>Email Address</Text>
<Text>Enter a valid email address.</Text>

3.3 TextInput

TextInput allows users to enter text. The value and onChangeText props are commonly used to create controlled inputs whose values are stored in React state.

import { useState } from 'react';
import { TextInput } from 'react-native';

function EmailInput() {
  const [email, setEmail] = useState('');

  return (
    <TextInput
      value={email}
      onChangeText={setEmail}
      placeholder="Enter your email"
      keyboardType="email-address"
      autoCapitalize="none"
    />
  );
}

3.4 Pressable

Pressable creates a touchable control for actions such as logging in, registering an account, or sending a message.

import { Pressable, Text } from 'react-native';

<Pressable onPress={() => console.log('Form submitted')}>
  <Text>Submit</Text>
</Pressable>

3.5 ScrollView

ScrollView makes content scrollable when a form contains multiple fields or when the on-screen keyboard reduces the available space. For longer forms, consider keyboard-aware layout techniques and test behavior on both iOS and Android.

import { ScrollView } from 'react-native';

<ScrollView keyboardShouldPersistTaps="handled">
  {/* Place form fields here */}
</ScrollView>

4. Understanding Controlled Form Inputs

A controlled input receives its displayed value from React state and updates that state through a callback. This approach makes it possible to validate input, clear fields, display errors, and submit form data.

4.1 Managing Multiple Fields

import { useState } from 'react';
import { View, Text, TextInput } from 'react-native';

function BasicForm() {
  const [form, setForm] = useState({
    name: '',
    email: '',
    password: ''
  });

  const updateField = (field, value) => {
    setForm(previous => ({
      ...previous,
      [field]: value
    }));
  };

  return (
    <View>
      <Text>Full Name</Text>
      <TextInput
        value={form.name}
        onChangeText={value => updateField('name', value)}
        placeholder="Enter your name"
      />

      <Text>Email</Text>
      <TextInput
        value={form.email}
        onChangeText={value => updateField('email', value)}
        keyboardType="email-address"
        autoCapitalize="none"
        placeholder="Enter your email"
      />

      <Text>Password</Text>
      <TextInput
        value={form.password}
        onChangeText={value => updateField('password', value)}
        secureTextEntry
        placeholder="Enter your password"
      />
    </View>
  );
}

4.2 Why Use a Shared State Object?

  • It groups related form values together.
  • It makes submission easier because values are available in one object.
  • A reusable update function reduces repeated code.
  • It supports validation across multiple fields.

When updating an object in React state, use the previous state and spread its properties. Otherwise, updating one field can accidentally remove the other fields.

5. Building a Login Form

5.1 Purpose of a Login Form

A login form allows an existing user to access an application. It normally collects an email address or username and a password. After client-side validation, the application sends the credentials to a trusted authentication backend.

5.2 Login Form Fields

Field Recommended Configuration Validation
Email keyboardType="email-address", autoCapitalize="none" Required; check basic email format.
Password secureTextEntry Required; follow backend authentication rules.
Login Button Pressable or another accessible button control Disabled or guarded while submitting.

5.3 Complete Login Form Example

import React, { useState } from 'react';
import {
  View,
  Text,
  TextInput,
  Pressable,
  StyleSheet,
  Alert
} from 'react-native';

export default function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [errors, setErrors] = useState({});
  const [loading, setLoading] = useState(false);

  const validate = () => {
    const nextErrors = {};

    if (!email.trim()) {
      nextErrors.email = 'Email is required.';
    } else if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email.trim())) {
      nextErrors.email = 'Enter a valid email address.';
    }

    if (!password) {
      nextErrors.password = 'Password is required.';
    }

    setErrors(nextErrors);
    return Object.keys(nextErrors).length === 0;
  };

  const handleLogin = async () => {
    if (loading || !validate()) return;

    setLoading(true);

    try {
      // Replace this example with your authentication API call.
      // Never treat client-side validation as proof of identity.
      await new Promise(resolve => setTimeout(resolve, 500));
      Alert.alert(
        'Demo Login',
        'Input validation passed. Connect an authentication service to sign in.'
      );
    } catch (error) {
      Alert.alert('Login failed', 'Please try again.');
    } finally {
      setLoading(false);
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Welcome Back</Text>
      <Text style={styles.subtitle}>Log in to continue.</Text>

      <Text style={styles.label}>Email Address</Text>
      <TextInput
        style={styles.input}
        value={email}
        onChangeText={setEmail}
        placeholder="[email protected]"
        keyboardType="email-address"
        autoCapitalize="none"
        autoCorrect={false}
        autoComplete="email"
        editable={!loading}
        accessibilityLabel="Email address"
      />
      {errors.email ? (
        <Text style={styles.error}>{errors.email}</Text>
      ) : null}

      <Text style={styles.label}>Password</Text>
      <TextInput
        style={styles.input}
        value={password}
        onChangeText={setPassword}
        placeholder="Enter your password"
        secureTextEntry
        autoCapitalize="none"
        autoComplete="current-password"
        editable={!loading}
        accessibilityLabel="Password"
      />
      {errors.password ? (
        <Text style={styles.error}>{errors.password}</Text>
      ) : null}

      <Pressable
        style={styles.button}
        onPress={handleLogin}
        disabled={loading}
        accessibilityRole="button"
        accessibilityState={{ disabled: loading, busy: loading }}
      >
        <Text style={styles.buttonText}>
          {loading ? 'Signing In...' : 'Login'}
        </Text>
      </Pressable>

      <Text style={styles.helper}>
        Forgot your password? Connect a secure password-reset flow.
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    padding: 20,
    gap: 8
  },
  title: {
    fontSize: 26,
    fontWeight: 'bold',
    marginBottom: 4
  },
  subtitle: {
    fontSize: 15,
    marginBottom: 16
  },
  label: {
    fontSize: 14,
    fontWeight: '600',
    marginTop: 8
  },
  input: {
    borderWidth: 1,
    borderColor: '#999',
    borderRadius: 8,
    padding: 12,
    fontSize: 16
  },
  error: {
    color: '#B00020',
    fontSize: 13
  },
  button: {
    backgroundColor: '#2457C5',
    borderRadius: 8,
    padding: 14,
    alignItems: 'center',
    marginTop: 16
  },
  buttonText: {
    color: '#FFFFFF',
    fontWeight: 'bold',
    fontSize: 16
  },
  helper: {
    marginTop: 12,
    fontSize: 13
  }
});

Important: This is a demonstration of form validation and loading feedback, not a real authentication implementation. Replace the simulated delay with a real authentication service. The backend must verify credentials and issue a secure session or token using an appropriate authentication design.

5.4 Login Form Best Practices

  • Use secure text entry for passwords.
  • Do not print passwords or authentication tokens in logs.
  • Do not store plaintext passwords on the device.
  • Show a clear message when authentication fails without exposing sensitive account details.
  • Prevent multiple submissions while a login request is running.
  • Provide a secure password-reset process.
  • Use HTTPS for communication with the authentication service.

6. Building a Registration Form

6.1 Purpose of Registration

A registration form collects information required to create a new user account. It generally includes a user's name, email address, password, and sometimes password confirmation or acceptance of terms.

6.2 Common Registration Fields

Field Purpose Validation Rule
Full Name Identifies the person in the application. Required; trim unnecessary leading and trailing spaces.
Email Supports account identification and communication. Required; validate basic format and verify ownership when appropriate.
Password Protects the account. Follow the application's password policy and backend requirements.
Confirm Password Helps users identify typing mistakes. Must match the password.
Terms Acceptance Records agreement where required. Must be selected if acceptance is required.

6.3 Registration Form Example

import React, { useState } from 'react';
import {
  View,
  Text,
  TextInput,
  Pressable,
  StyleSheet,
  Alert,
  ScrollView
} from 'react-native';

export default function RegistrationForm() {
  const [form, setForm] = useState({
    name: '',
    email: '',
    password: '',
    confirmPassword: ''
  });
  const [errors, setErrors] = useState({});
  const [loading, setLoading] = useState(false);

  const updateField = (field, value) => {
    setForm(previous => ({ ...previous, [field]: value }));
  };

  const validate = () => {
    const nextErrors = {};

    if (!form.name.trim()) {
      nextErrors.name = 'Full name is required.';
    }

    if (!form.email.trim()) {
      nextErrors.email = 'Email is required.';
    } else if (
      !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(form.email.trim())
    ) {
      nextErrors.email = 'Enter a valid email address.';
    }

    if (!form.password) {
      nextErrors.password = 'Password is required.';
    } else if (form.password.length < 8) {
      nextErrors.password = 'Use at least 8 characters.';
    }

    if (!form.confirmPassword) {
      nextErrors.confirmPassword = 'Confirm your password.';
    } else if (form.password !== form.confirmPassword) {
      nextErrors.confirmPassword = 'Passwords do not match.';
    }

    setErrors(nextErrors);
    return Object.keys(nextErrors).length === 0;
  };

  const handleRegister = async () => {
    if (loading || !validate()) return;

    setLoading(true);

    try {
      // Replace this demonstration with your registration API.
      // Send data over HTTPS and never send confirmPassword unless required.
      await new Promise(resolve => setTimeout(resolve, 500));
      Alert.alert(
        'Demo Registration',
        'Validation passed. Connect a backend to create the account.'
      );
    } catch (error) {
      Alert.alert('Registration failed', 'Please try again.');
    } finally {
      setLoading(false);
    }
  };

  return (
    <ScrollView
      contentContainerStyle={styles.container}
      keyboardShouldPersistTaps="handled"
    >
      <Text style={styles.title}>Create Account</Text>

      <Text style={styles.label}>Full Name</Text>
      <TextInput
        style={styles.input}
        value={form.name}
        onChangeText={value => updateField('name', value)}
        placeholder="Enter your full name"
        autoComplete="name"
        editable={!loading}
      />
      {errors.name ? <Text style={styles.error}>{errors.name}</Text> : null}

      <Text style={styles.label}>Email Address</Text>
      <TextInput
        style={styles.input}
        value={form.email}
        onChangeText={value => updateField('email', value)}
        placeholder="[email protected]"
        keyboardType="email-address"
        autoCapitalize="none"
        autoCorrect={false}
        autoComplete="email"
        editable={!loading}
      />
      {errors.email ? <Text style={styles.error}>{errors.email}</Text> : null}

      <Text style={styles.label}>Password</Text>
      <TextInput
        style={styles.input}
        value={form.password}
        onChangeText={value => updateField('password', value)}
        placeholder="Create a password"
        secureTextEntry
        autoComplete="new-password"
        editable={!loading}
      />
      {errors.password ? <Text style={styles.error}>{errors.password}</Text> : null}

      <Text style={styles.label}>Confirm Password</Text>
      <TextInput
        style={styles.input}
        value={form.confirmPassword}
        onChangeText={value => updateField('confirmPassword', value)}
        placeholder="Re-enter your password"
        secureTextEntry
        autoComplete="new-password"
        editable={!loading}
      />
      {errors.confirmPassword ? (
        <Text style={styles.error}>{errors.confirmPassword}</Text>
      ) : null}

      <Pressable
        style={styles.button}
        onPress={handleRegister}
        disabled={loading}
        accessibilityRole="button"
        accessibilityState={{ disabled: loading, busy: loading }}
      >
        <Text style={styles.buttonText}>
          {loading ? 'Creating Account...' : 'Register'}
        </Text>
      </Pressable>
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: {
    padding: 20,
    gap: 8
  },
  title: {
    fontSize: 26,
    fontWeight: 'bold',
    marginBottom: 12
  },
  label: {
    fontSize: 14,
    fontWeight: '600',
    marginTop: 8
  },
  input: {
    borderWidth: 1,
    borderColor: '#999',
    borderRadius: 8,
    padding: 12,
    fontSize: 16
  },
  error: {
    color: '#B00020',
    fontSize: 13
  },
  button: {
    backgroundColor: '#2457C5',
    borderRadius: 8,
    padding: 14,
    alignItems: 'center',
    marginTop: 16
  },
  buttonText: {
    color: '#FFFFFF',
    fontWeight: 'bold',
    fontSize: 16
  }
});

6.4 Registration Validation Considerations

  • Validate required fields before making a network request.
  • Compare the password and confirmation values exactly.
  • Keep password rules consistent with the backend authentication service.
  • Use server-side validation to detect duplicate accounts and invalid data.
  • Do not assume that client-side email validation proves the address exists.
  • Use email verification when the application requires proof of ownership.
  • Do not automatically log a user in unless the authentication flow is designed to do so securely.

7. Building a Contact Form

7.1 Purpose of a Contact Form

A contact form allows users to send questions, feedback, support requests, or business inquiries. Unlike login and registration forms, it generally does not require a password. Its main goal is to collect a message and deliver it to an authorized backend service.

7.2 Common Contact Form Fields

Field Recommended Input Validation
Name Single-line TextInput Required if the application needs identification.
Email Email keyboard Required if a reply address is needed; check basic format.
Subject Single-line TextInput Required or optional according to the business requirement.
Message Multiline TextInput Required; enforce sensible length limits.

7.3 Contact Form Example

import React, { useState } from 'react';
import {
  View,
  Text,
  TextInput,
  Pressable,
  StyleSheet,
  Alert
} from 'react-native';

export default function ContactForm() {
  const [form, setForm] = useState({
    name: '',
    email: '',
    subject: '',
    message: ''
  });
  const [errors, setErrors] = useState({});
  const [loading, setLoading] = useState(false);

  const updateField = (field, value) => {
    setForm(previous => ({ ...previous, [field]: value }));
  };

  const validate = () => {
    const nextErrors = {};

    if (!form.name.trim()) {
      nextErrors.name = 'Name is required.';
    }

    if (!form.email.trim()) {
      nextErrors.email = 'Email is required.';
    } else if (
      !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(form.email.trim())
    ) {
      nextErrors.email = 'Enter a valid email address.';
    }

    if (!form.subject.trim()) {
      nextErrors.subject = 'Subject is required.';
    }

    if (!form.message.trim()) {
      nextErrors.message = 'Message is required.';
    } else if (form.message.trim().length < 10) {
      nextErrors.message = 'Message must contain at least 10 characters.';
    }

    setErrors(nextErrors);
    return Object.keys(nextErrors).length === 0;
  };

  const handleSubmit = async () => {
    if (loading || !validate()) return;

    setLoading(true);

    try {
      // Replace this simulation with a request to your backend.
      await new Promise(resolve => setTimeout(resolve, 500));
      Alert.alert('Demo Submission', 'Validation passed successfully.');

      setForm({
        name: '',
        email: '',
        subject: '',
        message: ''
      });
      setErrors({});
    } catch (error) {
      Alert.alert('Submission failed', 'Please try again later.');
    } finally {
      setLoading(false);
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Contact Us</Text>
      <Text style={styles.subtitle}>Send us your question or feedback.</Text>

      <Text style={styles.label}>Name</Text>
      <TextInput
        style={styles.input}
        value={form.name}
        onChangeText={value => updateField('name', value)}
        placeholder="Your name"
        autoComplete="name"
        editable={!loading}
      />
      {errors.name ? <Text style={styles.error}>{errors.name}</Text> : null}

      <Text style={styles.label}>Email</Text>
      <TextInput
        style={styles.input}
        value={form.email}
        onChangeText={value => updateField('email', value)}
        placeholder="[email protected]"
        keyboardType="email-address"
        autoCapitalize="none"
        autoCorrect={false}
        autoComplete="email"
        editable={!loading}
      />
      {errors.email ? <Text style={styles.error}>{errors.email}</Text> : null}

      <Text style={styles.label}>Subject</Text>
      <TextInput
        style={styles.input}
        value={form.subject}
        onChangeText={value => updateField('subject', value)}
        placeholder="Reason for contacting us"
        editable={!loading}
      />
      {errors.subject ? <Text style={styles.error}>{errors.subject}</Text> : null}

      <Text style={styles.label}>Message</Text>
      <TextInput
        style={[styles.input, styles.message]}
        value={form.message}
        onChangeText={value => updateField('message', value)}
        placeholder="Write your message here"
        multiline
        textAlignVertical="top"
        maxLength={2000}
        editable={!loading}
        accessibilityLabel="Message"
      />
      {errors.message ? <Text style={styles.error}>{errors.message}</Text> : null}

      <Pressable
        style={styles.button}
        onPress={handleSubmit}
        disabled={loading}
        accessibilityRole="button"
        accessibilityState={{ disabled: loading, busy: loading }}
      >
        <Text style={styles.buttonText}>
          {loading ? 'Sending...' : 'Send Message'}
        </Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    padding: 20,
    gap: 8
  },
  title: {
    fontSize: 26,
    fontWeight: 'bold',
    marginBottom: 4
  },
  subtitle: {
    fontSize: 15,
    marginBottom: 12
  },
  label: {
    fontSize: 14,
    fontWeight: '600',
    marginTop: 8
  },
  input: {
    borderWidth: 1,
    borderColor: '#999',
    borderRadius: 8,
    padding: 12,
    fontSize: 16
  },
  message: {
    minHeight: 120
  },
  error: {
    color: '#B00020',
    fontSize: 13
  },
  button: {
    backgroundColor: '#2457C5',
    borderRadius: 8,
    padding: 14,
    alignItems: 'center',
    marginTop: 16
  },
  buttonText: {
    color: '#FFFFFF',
    fontWeight: 'bold',
    fontSize: 16
  }
});

Important: This example simulates a successful submission. To deliver real messages, connect the form to a backend API that validates the request, applies rate limits, and stores or forwards the message securely. Do not place private API keys, email-provider secrets, or administrative credentials in the mobile app.

8. Input Types and Keyboard Configuration

Choosing the right keyboard improves usability and reduces data-entry errors. React Native provides several useful TextInput properties.

Property Purpose Typical Use
keyboardType="default" Standard keyboard. Name, subject, general text.
keyboardType="email-address" Shows an email-friendly keyboard where supported. Email address.
keyboardType="phone-pad" Shows a phone-number keyboard. Telephone number.
secureTextEntry Obscures typed characters. Password.
multiline Allows text across multiple lines. Contact message or feedback.
autoCapitalize="none" Disables automatic capitalization. Email and password fields.
autoCorrect={false} Disables autocorrection. Email, username, or password.
maxLength Limits the number of characters entered. Message length or short text fields.
returnKeyType Changes the keyboard return-key label where supported. Next, done, or send workflows.
autoComplete Hints the type of data the operating system may autofill. Name, email, current password, new password.

Keyboard and autofill behavior can vary by operating system and device. Always test the form on both iOS and Android if both platforms are supported.

9. Form Validation and Error Handling

9.1 What Is Form Validation?

Form validation checks whether user input meets the requirements of the application before submission. It improves data quality and helps users correct mistakes.

9.2 Common Validation Rules

  • Required fields must not be empty.
  • Names and messages should be trimmed before checking whether they are empty.
  • Email fields should be checked for a basic valid format.
  • Password confirmation must match the original password.
  • Message fields should respect defined length limits.
  • Checkboxes or consent fields should be checked when the workflow requires them.

9.3 Reusable Validation Function

function validateContactForm(form) {
  const errors = {};

  if (!form.name.trim()) {
    errors.name = 'Name is required.';
  }

  if (!form.email.trim()) {
    errors.email = 'Email is required.';
  } else if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(form.email.trim())) {
    errors.email = 'Enter a valid email address.';
  }

  if (!form.message.trim()) {
    errors.message = 'Message is required.';
  }

  return errors;
}

This simple email pattern is a basic usability check, not a complete implementation of every valid email-address format. The backend should perform its own validation.

9.4 Displaying Field Errors

{errors.email ? (
  <Text accessibilityRole="alert">{errors.email}</Text>
) : null}

Place error messages near the relevant field. Use clear wording that explains how the user can fix the issue. Consider accessibility announcements and avoid relying on color alone to communicate errors.

9.5 Clearing or Updating Errors

Errors can be recalculated when the user submits the form or updated as the user types. For beginner projects, validation on submission is often easier to implement. For a more polished experience, consider validating after a field has been touched or after the first submission attempt.

10. Submission States and User Feedback

A reliable form communicates what is happening after a user presses its submit button.

State Expected Behavior
Idle Show the normal button label and allow input.
Invalid Display relevant validation errors and avoid submitting invalid data.
Loading Show progress feedback and prevent repeated submissions.
Success Confirm the action and reset or navigate as appropriate.
Failure Show a useful error message and allow the user to retry when appropriate.

Use try, catch, and finally around asynchronous requests so the loading state is reset even when an operation fails. Avoid displaying raw server errors, stack traces, or sensitive diagnostic details to end users.

11. Connecting Forms to a Backend API

Client-side form code collects and validates data, but a backend is required for real authentication, account creation, or message delivery. A typical form submission process includes the following steps.

  1. Read values from the controlled inputs.
  2. Run client-side validation.
  3. Stop submission if validation fails or a request is already running.
  4. Set the loading state.
  5. Send the required fields to the backend using an HTTPS request.
  6. Check the response and handle success or failure.
  7. Update the user interface and reset the loading state.

11.1 Example API Request Pattern

async function submitContactForm(form) {
  const response = await fetch('https://api.example.com/contact', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: form.name.trim(),
      email: form.email.trim(),
      subject: form.subject.trim(),
      message: form.message.trim()
    })
  });

  if (!response.ok) {
    throw new Error('Unable to submit the form.');
  }

  return response.json();
}

The domain api.example.com is a placeholder and must be replaced with your actual backend endpoint. This example assumes the endpoint accepts JSON and returns a JSON response. Adjust the request and response handling to match your API contract.

11.2 Important API Considerations

  • Use HTTPS to protect data in transit.
  • Validate all submitted data on the server.
  • Implement rate limiting and abuse protection for public forms.
  • Return suitable HTTP status codes and predictable error responses.
  • Handle network failures, timeouts, and server errors.
  • Do not trust a successful client-side validation result as a security control.
  • Do not hardcode private API secrets in the React Native application.
  • Use established authentication libraries or services where appropriate instead of designing an authentication protocol from scratch.

12. Keyboard Handling and Mobile Layout

Mobile forms can be difficult to use if the keyboard hides fields or the submit button. A good layout should adapt to smaller screens and allow users to reach every field.

  • Use ScrollView for forms that may exceed the available screen height.
  • Consider KeyboardAvoidingView where it improves keyboard behavior.
  • Test keyboard handling on both iOS and Android.
  • Use suitable padding and spacing between fields.
  • Set textAlignVertical="top" for multiline message fields where appropriate.
  • Consider moving focus between fields using input refs and the keyboard return key.
  • Keep the submit button visible or reachable when the keyboard is open.
import {
  KeyboardAvoidingView,
  Platform,
  ScrollView
} from 'react-native';

function FormScreen() {
  return (
    <KeyboardAvoidingView
      style={{ flex: 1 }}
      behavior={Platform.OS === 'ios' ? 'padding' : undefined}
    >
      <ScrollView
        contentContainerStyle={{ padding: 20 }}
        keyboardShouldPersistTaps="handled"
      >
        {/* Form fields and submit button */}
      </ScrollView>
    </KeyboardAvoidingView>
  );
}

Keyboard avoidance behavior depends on the screen layout, navigation configuration, and platform. Adjust and test it in the actual application instead of assuming one configuration works everywhere.

13. Accessibility and Usability Best Practices

Accessible forms are easier for more people to use, including people who rely on screen readers or larger text settings.

  • Provide clear labels for every input field.
  • Use descriptive button labels such as “Create Account” or “Send Message.”
  • Use accessibilityRole="button" for custom pressable buttons.
  • Use accessibilityLabel when a control's purpose is not clear from its visible text.
  • Make interactive controls large enough to tap comfortably.
  • Maintain sufficient contrast between text and background.
  • Do not communicate errors using color alone.
  • Keep validation messages specific and understandable.
  • Ensure forms remain usable with larger system font sizes.
  • Use appropriate focus order and test with screen readers.

14. Security Best Practices for Forms

  • Never store user passwords in plaintext.
  • Never log passwords, access tokens, or other authentication secrets.
  • Use a trusted authentication backend or established identity provider.
  • Transmit sensitive information only over HTTPS.
  • Validate and sanitize input on the server as appropriate for the data and its destination.
  • Apply rate limits and abuse protection to login, registration, and contact endpoints.
  • Use secure session and token handling appropriate to the authentication architecture.
  • Do not include private service credentials in application source code.
  • Return generic authentication failure messages when needed to reduce account enumeration risks.
  • Use server-side authorization for protected operations; hiding a screen is not a security boundary.

15. Reusable Form Components

When several screens use similar inputs, reusable components reduce duplication and help maintain a consistent design.

15.1 Reusable Input Component

import React from 'react';
import { View, Text, TextInput, StyleSheet } from 'react-native';

function FormField({
  label,
  value,
  onChangeText,
  error,
  ...inputProps
}) {
  return (
    <View style={styles.field}>
      <Text style={styles.label}>{label}</Text>
      <TextInput
        style={styles.input}
        value={value}
        onChangeText={onChangeText}
        accessibilityLabel={label}
        {...inputProps}
      />
      {error ? (
        <Text style={styles.error} accessibilityRole="alert">
          {error}
        </Text>
      ) : null}
    </View>
  );
}

const styles = StyleSheet.create({
  field: {
    marginBottom: 12
  },
  label: {
    fontWeight: '600',
    marginBottom: 6
  },
  input: {
    borderWidth: 1,
    borderColor: '#999',
    borderRadius: 8,
    padding: 12
  },
  error: {
    color: '#B00020',
    marginTop: 4
  }
});

export default FormField;

15.2 Example Usage

<FormField
  label="Email Address"
  value={email}
  onChangeText={setEmail}
  error={errors.email}
  placeholder="[email protected]"
  keyboardType="email-address"
  autoCapitalize="none"
  autoCorrect={false}
/>

Reusable components can also accept props for helper text, secure text entry, multiline input, maximum length, and accessibility configuration. Keep the component simple enough that each screen can still define its own validation and submission behavior.

16. Suggested Project Structure

As an application grows, separating screens, reusable components, and API functions makes the code easier to understand and maintain.

src/
  components/
    FormField.js
    PrimaryButton.js
    FormError.js
  screens/
    LoginScreen.js
    RegistrationScreen.js
    ContactScreen.js
  services/
    authService.js
    contactService.js
  utils/
    validation.js
  navigation/
    AppNavigator.js

This is one possible structure, not a required convention. Small practice projects may keep the code in fewer files, while larger applications may separate hooks, types, tests, and feature-specific modules.

17. Common Problems and Troubleshooting

Problem Possible Cause Solution
Input does not update. Missing or incorrect state update in onChangeText. Check that value is connected to state and the callback updates it.
Other fields become empty after an update. State object is overwritten instead of merged. Use the previous state and spread its existing properties.
Password is visible. secureTextEntry is missing. Enable secure text entry for password fields.
Keyboard hides the submit button. Layout does not account for the keyboard. Test a scrollable layout and appropriate keyboard avoidance.
Invalid data reaches the backend. Only some fields are validated, or only client-side validation is used. Validate required fields in the app and independently validate on the server.
Button submits several times. Repeated taps are not guarded. Use a loading state and prevent duplicate in-flight submissions.
Form stays in loading state. Loading state is not reset after an error. Reset it in a finally block.
Error message is confusing. Generic or technical wording. Explain the problem and how to fix it.
Contact form does not deliver messages. No real API is connected or the server rejects the request. Verify the endpoint, payload, server response, and network behavior.
Registration fails for an existing email. The backend enforces unique email addresses. Display a safe, clear response and provide an appropriate next step.

18. Testing Login, Registration, and Contact Forms

Testing should cover normal usage, invalid input, network behavior, and accessibility. Do not test only the happy path.

18.1 Login Form Test Cases

  • Submit both fields empty.
  • Enter an invalid email format.
  • Leave the password empty.
  • Submit valid-looking credentials with an authentication service connected.
  • Test incorrect credentials and network failure.
  • Tap the login button repeatedly during submission.
  • Check password masking and password-reset navigation.

18.2 Registration Form Test Cases

  • Submit all fields empty.
  • Enter a name with leading or trailing spaces.
  • Enter an invalid email address.
  • Enter a password that does not meet the defined policy.
  • Enter mismatched password confirmation.
  • Test an email address that already exists.
  • Test backend validation and account-verification behavior.

18.3 Contact Form Test Cases

  • Submit without a name, email, subject, or message when each is required.
  • Enter an invalid email address.
  • Submit a message that is too short or too long.
  • Test network failure and server errors.
  • Confirm that a successful submission gives clear feedback.
  • Verify that fields are reset only when appropriate after success.
  • Test abuse protection and server-side input validation.

19. Practical Project: Authentication and Contact Screens

Build a small React Native application with three screens: Login, Registration, and Contact Us. Each screen should use reusable components and provide clear validation feedback.

Project Requirements

  1. Create a Login screen with email and password fields.
  2. Create a Registration screen with name, email, password, and confirm-password fields.
  3. Create a Contact screen with name, email, subject, and multiline message fields.
  4. Create a reusable FormField component.
  5. Create a validation utility for required fields and email format.
  6. Display field-level error messages.
  7. Show loading feedback while requests are running.
  8. Prevent repeated submissions while a request is in progress.
  9. Use a backend or test API for actual submission behavior.
  10. Test the application on at least one Android or iOS device or simulator, and test both platforms if both are supported.
  11. Document the validation rules and API response behavior.

Expected Outcome

The completed project should demonstrate controlled inputs, reusable form components, validation, submission states, error handling, responsive layouts, accessibility basics, and clear separation between UI code and backend services.

20. Exercises for Practice

  1. Create a login form with email and password validation.
  2. Add a show/hide password control with an accessible label.
  3. Build a registration form and validate password confirmation.
  4. Add a terms-and-conditions checkbox to registration if required by your use case.
  5. Build a contact form with a multiline message input.
  6. Create a reusable component that displays a field label, input, and validation error.
  7. Add a loading indicator and disable the submit button during a request.
  8. Connect the contact form to a test backend endpoint.
  9. Handle unsuccessful HTTP responses and network errors.
  10. Test form behavior with a keyboard open, larger text, and screen-reader navigation.

21. Interview Questions and Answers

Q1. What is a form in React Native?

A form is a collection of input controls that gathers information from a user and allows the application to validate and submit that information.

Q2. Which component is commonly used to accept user input?

TextInput is the standard React Native component for accepting text input.

Q3. What is a controlled input?

A controlled input gets its value from React state and updates that state through an event handler such as onChangeText.

Q4. What is the difference between login and registration forms?

A login form authenticates an existing account, while a registration form collects the information required to create a new account.

Q5. Why is secure text entry used?

The secureTextEntry prop obscures password characters as the user types, helping prevent casual observation of the input.

Q6. Why is client-side validation important?

It gives users quick feedback, prevents avoidable requests, and improves the quality of data submitted by the interface.

Q7. Is client-side validation enough for security?

No. Users can bypass the app interface. The backend must independently validate submitted data and enforce authentication and authorization rules.

Q8. How do you handle loading during form submission?

Use a state variable to indicate an active request, show progress feedback, prevent repeated submissions, and reset the state in a finally block.

Q9. Why is a ScrollView useful for forms?

It lets users scroll through fields when content is taller than the available screen space, which is especially useful on smaller devices.

Q10. How can multiple fields share state?

Store related values in an object and update a specific field using a state updater that preserves the previous object's other properties.

Q11. Why should private API keys not be stored in a React Native app?

Application code and bundled assets can be inspected. Private credentials should remain on a secure server.

Q12. What is the purpose of a contact form?

It collects inquiries, feedback, or support requests and sends them to a backend service for processing.

Q13. Why should forms provide accessible labels?

Labels help users understand the purpose of each field and support people using assistive technologies such as screen readers.

Q14. What is the role of a reusable form component?

It reduces duplicated code and creates a consistent interface across different forms and screens.

Q15. What should happen when a form submission fails?

The application should stop showing a loading state, present a useful and safe error message, preserve input where appropriate, and allow a retry when suitable.

22. Quick Revision Checklist

  • Understand the purpose of login, registration, and contact forms.
  • Use TextInput with state and onChangeText.
  • Choose suitable keyboard types and autofill hints.
  • Use secureTextEntry for password fields.
  • Validate required fields and display clear errors.
  • Check password confirmation in registration forms.
  • Use multiline input for long contact messages.
  • Handle loading, success, and failure states.
  • Use reusable input and button components when appropriate.
  • Support scrolling and keyboard interactions on mobile devices.
  • Use accessible labels and descriptive button text.
  • Validate all data on the backend and use HTTPS.
  • Never store or log plaintext passwords or private credentials.
  • Test empty input, invalid input, network failures, and repeated taps.

23. Conclusion

Building login, registration, and contact forms is an important skill in React Native development. These forms use core components such as View, Text, TextInput, Pressable, and ScrollView to collect user information and provide feedback.

Effective forms combine controlled inputs, validation, clear error messages, loading states, responsive layouts, accessibility, and reliable backend integration. For production applications, remember that client-side validation improves usability but does not replace server-side validation, secure authentication, or authorization.

Continue learning through the React Native Training Course, visit the JustAcademy website, or register for a course demo.

24. Additional Learning Resources

whatsapp